package com.demo.aspect;

import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson2.JSON;
import com.demo.annotation.RepeatSubmit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * @author wangfengchen
 * 定义一个切面类
 */
@Slf4j
@Component
@Aspect
public class RepeatSubmitAspect {

  /**
   * 重复缓存key
   */
  public static final String REPEAT_SUBMIT_KEY = "METEOR:REPEAT-SUBMIT:";

  @Resource
  private RedissonClient redissonClient;

  /**
   * 获取HttpServletRequest对象
   *
   * @return
   */
  public static HttpServletRequest getRequest() {
    HttpServletRequest request = null;
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    if (requestAttributes != null) {
      request = ((ServletRequestAttributes) requestAttributes).getRequest();
    }
    return request;
  }

  /**
   * 获取HttpServletRequest请求Body中的内容
   *
   * @param request
   * @return3
   */
  private static String getBody(HttpServletRequest request) {
    try (InputStream is = request.getInputStream()) {
      return IOUtils.toString(is, String.valueOf(StandardCharsets.UTF_8));
    } catch (IOException ex) {
      log.error("read http request failed.", ex);
    }
    return null;
  }

  /**
   * 方式一：@annotation：当执行的方法上拥有指定的注解时生效
   * 方式二：execution：一般用于指定方法的执行
   *
   * @param repeatSubmit
   */
  @Pointcut("@annotation(repeatSubmit)")
  public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit) {
    throw new UnsupportedOperationException();
  }

  /**
   * 方式一：加锁，固定时间内不能重复提交
   * 方式二：先请求获取token，再删除token,删除成功则是第一次提交
   *
   * @param joinPoint
   * @param repeatSubmit
   * @return
   * @throws Throwable
   */
  @Around("pointCutNoRepeatSubmit(repeatSubmit)")
  public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {
    HttpServletRequest request = getRequest();
    if (request == null) {
      log.error("获取request失败");
      throw new RuntimeException();
    }
    StringBuilder param = new StringBuilder();
    // 获取token
    String token = request.getHeader("token");
    if (StringUtils.isNotBlank(token)) {
      String decodeToken = URLDecoder.decode(token, String.valueOf(StandardCharsets.UTF_8));
      param.append(decodeToken);
    }
    // 获取url路径请求参数
    Map<String, String[]> map = request.getParameterMap();
    if (ObjectUtils.isNotEmpty(map)) {
      String paramGet = JSON.toJSONString(map);
      param.append(paramGet);
    }
    // 获取body请求参数
    String paramPost = getBody(request);
    if (StringUtils.isNotBlank(paramPost)) {
      param.append(paramPost);
    }
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    Method method = methodSignature.getMethod();
    String className = method.getDeclaringClass().getName();
    String key = REPEAT_SUBMIT_KEY + SecureUtil.md5(String.format("%s-%s-%s", className, method.getName(), param));
    RLock lock = redissonClient.getLock(key);
    Object obj = null;
    try {
      if (lock.tryLock()) {
        try {
          log.info("环绕通知执行前");
          obj = joinPoint.proceed();
          log.info("环绕通知执行后");
        } finally {
          // 解锁
          lock.unlock();
        }
      } else {
        // 加锁失败
        log.error("请求重复提交");
        throw new RuntimeException();
      }
    } catch (InterruptedException e) {
      // 重新设置一下中断标示，以便上层调用者知道线程被中断了，因为抛出InterruptedException异常后，中断标示位会自动清除
      Thread.currentThread().interrupt();
    }
    return obj;
  }

}
